package com.ruoyi.common.cmb.utils;

import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.cmb.constant.CmbDevConfigConstants;
import com.ruoyi.common.cmb.entity.BB1PAYOPOneParam;
import com.ruoyi.common.cmb.entity.BB1PAYOPTwoParam;
import com.ruoyi.common.cmb.entity.BB1PAYQRXOneParam;
import com.ruoyi.common.cmb.entity.BankSMConfigDto;
import com.ruoyi.common.cmb.enums.CMBTransAction;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.*;
import java.io.*;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 招商银行国密工具类
 */
public class CMBSMUtils {
    private static final int LENGTH_32 = 32;
    private static final int USERID_LEN = 16;
    private static final int CONNECT_TIMEOUT = 15000;
    private static final int READ_TIMEOUT = 60000;
    private static final int STATUS_OK = 200;
    private static Base64.Encoder encoder = Base64.getEncoder();
    private static Base64.Decoder decoder = Base64.getDecoder();

    private final Logger logger = LoggerFactory.getLogger(getClass());

    // 请求URL
    private static String url;

    // 企业网银用户号
    private static String uid;

    // 国密算法向量，根据用户号生成
    private static byte[] userId;

    // 算法，固定为国密算法
    private static String alg;

    // 客户私钥
    private static byte[] privateKey;

    // 银行公钥
    private static byte[] publicKey;

    // 协商的对称密钥
    private static byte[] symKey;

    public static void main(String[] args) throws Exception {
//        bb1payop();
        bb1payqr();

    }

    /**
     * 企银支付单笔经办BB1PAYOP
     */
    public static void bb1payop () throws Exception {
        BankSMConfigDto bankSMConfig = new BankSMConfigDto();
        bankSMConfig.setPublicKey(CmbDevConfigConstants.PUBLICKEY);
        bankSMConfig.setPrivateKey(CmbDevConfigConstants.PRIVATEKEY);
        bankSMConfig.setSymKey(CmbDevConfigConstants.SYMKEY);
        bankSMConfig.setUID(CmbDevConfigConstants.UID);

        bankSMConfig.setBbknbr("75");
        bankSMConfig.setPayacc(CmbDevConfigConstants.PAYACC);
        bankSMConfig.setBusmod("N02030");
        bankSMConfig.setGrtflg("N");
        bankSMConfig.setURL(CmbDevConfigConstants.URL);

        //============================企银支付单笔经办 接口测试================================
        BB1PAYOPOneParam bb1PAYOPOneParam = new BB1PAYOPOneParam();
        bb1PAYOPOneParam.setBusMod("S3010");
        bb1PAYOPOneParam.setBusCod("N02030");
        List<BB1PAYOPOneParam> bb1paybmx1 = Arrays.asList(bb1PAYOPOneParam);
        BB1PAYOPTwoParam bb1PAYOPTwoParam = new BB1PAYOPTwoParam();
        bb1PAYOPTwoParam.setDbtAcc(CmbDevConfigConstants.PAYACC);
        bb1PAYOPTwoParam.setBrdNbr("102100099996");
        bb1PAYOPTwoParam.setCrtAcc("123456789");
        bb1PAYOPTwoParam.setCrtNam("效益高有限公司");
        bb1PAYOPTwoParam.setCcyNbr("10");
        bb1PAYOPTwoParam.setTrsAmt("42.00");
        bb1PAYOPTwoParam.setNusAge("测试给它行的账号转账");
        bb1PAYOPTwoParam.setCrtBnk("中国建设银行北碚分行");
        bb1PAYOPTwoParam.setCrtAdr("a");
        bb1PAYOPTwoParam.setYurRef("202502100013");

        List<BB1PAYOPTwoParam> bb1payopx1 = Arrays.asList(bb1PAYOPTwoParam);

        Map<String, Object> body = new HashMap<>();
        body.put("bb1paybmx1", bb1paybmx1);
        body.put("bb1payopx1", bb1payopx1);

        List<String> BB1PAYOPEmptyFiledList = Arrays.asList("dmaNbr", "crtBnk", "crtAdr", "brdNbr", "bnkFlg", "eptDat", "eptTim", "stlChn", "crtSqn", "busNar", "ntfCh1", "ntfCh2", "trsTyp", "rcvChk", "drpFlg");

        JSONObject BB1PAYOPResponse = commonRequestMethod(bankSMConfig, CMBTransAction.ProxyPay.getCode(), body, BB1PAYOPEmptyFiledList, null);
        System.out.println("企银支付单笔经办返回信息： " + BB1PAYOPResponse);


        JSONObject response = BB1PAYOPResponse.getJSONObject("response");
        JSONObject bodyxxx = response.getJSONObject("body");
        List body1 = bodyxxx.getObject("bb1payopz1", List.class);
        System.out.println("body1 = " + body1);

        Map map = JSONObject.parseObject(Convert.toStr(body1.get(0)), Map.class);
        System.out.println("map = " + map);
    }

    /**
     * 企银支付单笔经办 BB1PAYOP
     */
    public static JSONObject bb1payopProd (BB1PAYOPOneParam bb1PAYOPOneParam,BB1PAYOPTwoParam bb1PAYOPTwoParam) throws Exception {
        BankSMConfigDto bankSMConfig = new BankSMConfigDto();
        bankSMConfig.setPublicKey(CmbDevConfigConstants.PUBLICKEY);
        bankSMConfig.setPrivateKey(CmbDevConfigConstants.PRIVATEKEY);
        bankSMConfig.setSymKey(CmbDevConfigConstants.SYMKEY);
        bankSMConfig.setUID(CmbDevConfigConstants.UID);
        bankSMConfig.setBbknbr("75");
        bankSMConfig.setPayacc(CmbDevConfigConstants.PAYACC);
        bankSMConfig.setBusmod(CmbDevConfigConstants.BUSMOD);
        bankSMConfig.setGrtflg("N");
        bankSMConfig.setURL(CmbDevConfigConstants.URL);

        List<BB1PAYOPOneParam> bb1paybmx1 = Arrays.asList(bb1PAYOPOneParam);
        List<BB1PAYOPTwoParam> bb1payopx1 = Arrays.asList(bb1PAYOPTwoParam);

        Map<String, Object> body = new HashMap<>();
        body.put("bb1paybmx1", bb1paybmx1);
        body.put("bb1payopx1", bb1payopx1);
        List<String> BB1PAYOPEmptyFiledList = Arrays.asList("dmaNbr", "crtBnk", "crtAdr", "brdNbr", "bnkFlg", "eptDat", "eptTim", "stlChn", "crtSqn", "busNar", "ntfCh1", "ntfCh2", "trsTyp", "rcvChk", "drpFlg");
        return commonRequestMethod(bankSMConfig, CMBTransAction.ProxyPay.getCode(), body, BB1PAYOPEmptyFiledList, null);
    }

    /**
     * 企银支付业务查询BB1PAYQR
     */
    public static JSONObject bb1payqr () throws Exception {
        BankSMConfigDto bankSMConfig = new BankSMConfigDto();
        bankSMConfig.setPublicKey(CmbDevConfigConstants.PUBLICKEY);
        bankSMConfig.setPrivateKey(CmbDevConfigConstants.PRIVATEKEY);
        bankSMConfig.setSymKey(CmbDevConfigConstants.SYMKEY);
        bankSMConfig.setUID(CmbDevConfigConstants.UID);
        bankSMConfig.setURL(CmbDevConfigConstants.URL);

        BB1PAYQRXOneParam bb1PAYQRXOneParam = new BB1PAYQRXOneParam();
        bb1PAYQRXOneParam.setBusCod("N02030");
        bb1PAYQRXOneParam.setYurRef("25030866135362");
        List<BB1PAYQRXOneParam> bb1payqrx1 = Arrays.asList(bb1PAYQRXOneParam);
        Map<String, Object> body = new HashMap<>();
        body.put("bb1payqrx1", bb1payqrx1);

        JSONObject object = commonRequestMethod(bankSMConfig, CMBTransAction.BB1PAYQR.getCode(), body, null, null);
        System.out.println("企银支付业务查询： " + object);
        return object;
    }

    /**
     * 企银支付业务查询BB1PAYQR
     */
    public static JSONObject bb1payqrProd (String yurRef) throws Exception {
        BankSMConfigDto bankSMConfig = new BankSMConfigDto();
        bankSMConfig.setPublicKey(CmbDevConfigConstants.PUBLICKEY);
        bankSMConfig.setPrivateKey(CmbDevConfigConstants.PRIVATEKEY);
        bankSMConfig.setSymKey(CmbDevConfigConstants.SYMKEY);
        bankSMConfig.setUID(CmbDevConfigConstants.UID);
        bankSMConfig.setURL(CmbDevConfigConstants.URL);

        BB1PAYQRXOneParam bb1PAYQRXOneParam = new BB1PAYQRXOneParam();
        bb1PAYQRXOneParam.setBusCod("N02030");
        bb1PAYQRXOneParam.setYurRef(yurRef);
        List<BB1PAYQRXOneParam> bb1payqrx1 = Arrays.asList(bb1PAYQRXOneParam);
        Map<String, Object> body = new HashMap<>();
        body.put("bb1payqrx1", bb1payqrx1);
        return commonRequestMethod(bankSMConfig, CMBTransAction.BB1PAYQR.getCode(), body, null, null);
    }

    /**
     * 初始化银行配置
     * @param bankSMConfig
     */
    private static void init(BankSMConfigDto bankSMConfig) {
        // 装载BC库,必须在应用的启动类中调用此函数
        Security.addProvider(new BouncyCastleProvider());

        url = bankSMConfig.getURL();
        uid = bankSMConfig.getUID();
        userId = getUserId(uid);
        alg = "SM";
        privateKey = decoder.decode(bankSMConfig.getPrivateKey());
        publicKey = decoder.decode(bankSMConfig.getPublicKey());
        symKey = bankSMConfig.getSymKey().getBytes(StandardCharsets.UTF_8);
    }

    /**
     * 银行接口公共请求方法
     * @param bankSMConfig  银行相关配置
     * @param funcode  银行接口的funcode
     * @param body  银行接口的body属性值，目前只有Object和Map<String, Object>两种类型
     * @param emptyFiledList  body属性中可以为空的属性值，没有传null
     * @param message  在为空的基础上，前面添加的提示信息，没有传null
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> JSONObject commonRequestMethod(BankSMConfigDto bankSMConfig, String funcode, T body, List<String> emptyFiledList, String message) throws Exception {
        JSONObject requestJson = getRequestJson(bankSMConfig, funcode, body, emptyFiledList, message);

        init(bankSMConfig);

        //对请求报文做排序
        String source = recursiveKeySort(requestJson);

        // 生成签名
        byte[] signature = cmbSM2SignWithSM3(userId, privateKey, source.getBytes(StandardCharsets.UTF_8));

        //请求体的signature
        JSONObject reqSignatureJson = requestJson.getJSONObject("signature");

        // 替换签名字段
        reqSignatureJson.put("sigdat", new String(encoder.encode(signature), StandardCharsets.UTF_8));

        // 对数据进行对称加密
        String request = requestJson.toString();
        byte[] encryptRequest = cmbSM4Crypt(symKey, userId, request.getBytes(StandardCharsets.UTF_8), 1);
        String encryptedRequest = new String(encoder.encode(encryptRequest), StandardCharsets.UTF_8);

        // 发送请求
        HashMap<String, String> map = new HashMap<>();
        map.put("UID", uid);
        map.put("ALG", alg);
        map.put("DATA", URLEncoder.encode(encryptedRequest, StandardCharsets.UTF_8.displayName()));
        map.put("FUNCODE", funcode);

        String response = httpPost(url, map);
        if (response.startsWith("CDCServer:")) {
            throw new IOException("访问目标地址 " + url + " 失败:" + response);
        }

        // 返回结果解密
        response = new String((cmbSM4Crypt(symKey, userId, decoder.decode(response), 2)), StandardCharsets.UTF_8);

        // 验证签名是否正确
        JSONObject responseJson = JSONObject.parseObject(response);

        JSONObject resSignatureJson = responseJson.getJSONObject("signature");
        String responseSignature = resSignatureJson.getString("sigdat");
        resSignatureJson.put("sigdat", "__signature_sigdat__");
        responseJson.put("signature", resSignatureJson);
        String responseSorted = recursiveKeySort(responseJson);
        boolean verify = cmbSM2VerifyWithSM3(userId, publicKey, responseSorted.getBytes(StandardCharsets.UTF_8), decoder.decode(responseSignature));
        if (!verify) {
            throw new IOException("响应报文的签名无效");
        }
        return responseJson;
    }

    /**
     * 获取请求参数json
     * @param bankSMConfig  银行接口参数，这里用来校验是否为空，这个方法只使用到了UID
     * @param funcode 接口code
     * @param body  请求json中的参数body，泛型类型
     * @return
     */
    private static <T> JSONObject getRequestJson(BankSMConfigDto bankSMConfig, String funcode, T body, List<String> emptyFiledList, String message) {
        List<String> noEmptyFieldList = Arrays.asList("publicKey", "privateKey", "symKey", "UID", "URL");
        VerifyUtil.checkBeanByNonEmptyFiledList(bankSMConfig, noEmptyFieldList, "银行配置");

        VerifyUtil.checkParam(funcode, "funcode不能为空！");

        //校验body参数
        checkBody(body, emptyFiledList, message);

        String data = "{\"request\":{\"body\":" + JSONObject.toJSONString(body) + ",\"head\":{\"funcode\":\"" + funcode + "\",\"reqid\":\"" + getReqId() + "\",\"userid\":\"" + bankSMConfig.getUID() + "\"}},\"signature\":{\"sigdat\":\"__signature_sigdat__\",\"sigtim\":\"" + getCurrentDatetime() + "\"}}";

        return JSONObject.parseObject(data);
    }

    /**
     * 校验银行body参数
     * @param body  body参数
     * @param emptyFiledList  可以为空的属性集合，为JavaBean的属性，没有可以为空的传null
     * @param message  在为空的基础上，前面添加的提示信息，没有传null
     */
    public static <T> void checkBody(T body, List<String> emptyFiledList, String message) {
        if (body instanceof Map) {
            Map<String, Object> map = (Map<String, Object>) body;
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();

                List list = null;
                if (value instanceof List) {
                    list = (List) value;
                } else {
                    throw new RuntimeException("map集合中key为【" + key + "】的value值非法！应为List！");
                }

                checkList(list, emptyFiledList, message);
            }
        } else {
            VerifyUtil.checkBeanByEmptyFiledList(body, emptyFiledList, message);
        }
    }

    /**
     * 校验参数集合
     * @param list  参数集合
     * @param emptyFiledList  可以为空的属性集合，为JavaBean的属性，没有可以为空的传null
     * @param message  在为空的基础上，前面添加的提示信息，没有传null
     */
    public static <T> void checkList(List<T> list, List<String> emptyFiledList, String message) {
        VerifyUtil.checkParam(list, (StringUtils.isBlank(message) ? "集合" : message) + "为空！");

        for (int i = 0; i < list.size(); i++) {
            T t = list.get(i);

            VerifyUtil.checkBean(t, emptyFiledList, true, (StringUtils.isBlank(message) ? "集合的" : message + "的") + "第【" + i + "】条记录");
        }
    }

    //成功字符串
    private static final String SUC0000 = "SUC0000";

    //响应参数字符串
    private static final String RESPONSE = "response";

    //响应参数中head参数字符串
    private static final String HEAD = "head";

    //响应参数中body参数字符串
    private static final String BODY = "body";

    /**
     * 判断银行调用结果是否成功
     * @param responseJson 银行响应结果json
     * @return
     */
    public static Boolean isSuccess(JSONObject responseJson) {
        return SUC0000.equals(getCmbResponseParam(responseJson, HEAD, "resultcode"));
    }

    /**
     * 获取调用银行接口的响应参数值，不能通过该方法获取signature参数下的值
     * @param responseJson 银行响应结果json
     * @param param1 要获取的结果参数名 （一级或者二级）
     * @param param2 要获取的结果参数名 （三级，没有可以为空，如果是body下的参数则一定要写正确）
     * @return
     */
    public static String getCmbResponseParam(JSONObject responseJson, String param1, String param2) {

        JSONObject response = (JSONObject) responseJson.get(RESPONSE);
        VerifyUtil.checkParam(response, "返回结果中【response】为空或传入param1有误！");

        JSONObject head = (JSONObject) response.get(HEAD);
        VerifyUtil.checkParam(head, "返回结果中【head】为空或传入param1有误！");

        JSONObject body = (JSONObject) response.get(BODY);
        VerifyUtil.checkParam(body, "返回结果中【body】为空或传入param1有误！");

        //head下的参数
        List<String> param2List = Arrays.asList("bizcode", "funcode", "reqid", "resultcode", "resultmsg", "rspid", "userid");

        String result = null;
        if (RESPONSE.equals(param1)) {
            result = response.toString();
        } else if (HEAD.equals(param1)) {
            result = VerifyUtil.isNotEmpty(param2) && param2List.contains(param2) ? head.get(param2).toString() : head.toString();
        } else if (BODY.equals(param1)) {
            result = VerifyUtil.isNotEmpty(param2) ? body.get(param2).toString() : body.toString();
        } else {
            throw new RuntimeException("传入param2时param1不能为空！");
        }

        return result;
    }

    private static final int BOUND_START = 1000000;

    private static final int BOUND_END = 9000000;

    //reqid时间格式
    private static final String reqIdFormat = "yyyyMMddHHmmssSSSS";

    //当前时间格式
    private static final String currentDatetimeFormat = "yyyyMMddHHmmss";

    private static Random random = new Random();

    /**
     * 获取请求id
     * @return
     */
    private static String getReqId() {
        return getTargetTime(reqIdFormat) + (BOUND_START + random.nextInt(BOUND_END));
    }

    /**
     * 获取当前时间字符
     * @return
     */
    private static String getCurrentDatetime() {
        return getTargetTime(currentDatetimeFormat);
    }

    /**
     * 根据时间格式返回指定时间字符串
     * @param pattern  时间格式
     * @return
     */
    private static String getTargetTime(String pattern) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
        return simpleDateFormat.format(new Date());
    }


    /**
     * 对请求报文做排序
     * @param json
     * @return  注意返回类型不能再转化为JSONObject，不然会打乱原有的排序
     */
    private static String recursiveKeySort(JSONObject json) {
        StringBuilder appender = new StringBuilder();
        appender.append("{");
        Iterator<String> keys = new TreeSet<>(json.keySet()).iterator();
        boolean isFirstEle = true;
        while (keys.hasNext()) {
            if (!isFirstEle) {
                appender.append(",");
            }
            String key = keys.next();
            Object val = json.get(key);
            if (val instanceof JSONObject) {
                appender.append("\"").append(key).append("\":");
                appender.append(recursiveKeySort((JSONObject) val));
            } else if (val instanceof JSONArray) {
                JSONArray jarray = (JSONArray) val;
                appender.append("\"").append(key).append("\":[");
                boolean isFirstArrEle = true;
                for (int i = 0; i < jarray.size(); i++) {
                    if (!isFirstArrEle) {
                        appender.append(",");
                    }
                    Object obj = jarray.get(i);
                    if (obj instanceof JSONObject) {
                        appender.append(recursiveKeySort((JSONObject) obj));
                    } else {
                        appender.append(obj.toString());
                    }
                    isFirstArrEle = false;
                }
                appender.append("]");
            } else {
                String value = val.toString();
                appender.append("\"").append(key).append("\":\"").append(value).append("\"");
            }
            isFirstEle = false;
        }
        appender.append("}");
        return appender.toString();
    }

    private static byte[] getUserId(String uid) {
        return (uid + "0000000000000000").substring(0, USERID_LEN).getBytes();
    }

    private static String httpPost(String httpUrl, Map<String, String> param) throws IOException, GeneralSecurityException {
        HttpURLConnection connection = null;
        String result;
        try {
            URL url = new URL(httpUrl);
            SSLContext sslcontext;
            sslcontext = SSLContext.getInstance("SSL");
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init((KeyStore)null);
            X509TrustManager defaultTm = null;
            for (TrustManager tm : tmf.getTrustManagers()) {
                if (tm instanceof X509TrustManager) {
                    defaultTm = (X509TrustManager)tm;
                    break;
                }
            }
            sslcontext.init(null, new TrustManager[]{defaultTm}, new java.security.SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sslcontext.getSocketFactory());

            connection = (HttpURLConnection)url.openConnection();
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(CONNECT_TIMEOUT);
            connection.setReadTimeout(READ_TIMEOUT);
            connection.setInstanceFollowRedirects(true);

            connection.setDoOutput(true);
            connection.setDoInput(true);

            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            try (OutputStream os = connection.getOutputStream()) {
                os.write(createLinkString(param).getBytes());
                if (connection.getResponseCode() != STATUS_OK) {
                    InputStream is = connection.getErrorStream();
                    BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
                    StringBuilder sbf = new StringBuilder();
                    String temp;
                    while ((temp = br.readLine()) != null) {
                        sbf.append(temp);
                        sbf.append("\r\n");
                    }

                    result = sbf.toString();
                    br.close();
                    is.close();
                } else {
                    InputStream is = connection.getInputStream();
                    BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
                    StringBuilder sbf = new StringBuilder();
                    String temp;
                    while ((temp = br.readLine()) != null) {
                        sbf.append(temp);
                    }
                    result = sbf.toString();
                    br.close();
                    is.close();
                }
            }
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
        return result;
    }

    private static String createLinkString(Map<String, String> params) {
        ArrayList<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);

        StringBuilder prestr = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {
                prestr.append(key).append("=").append(value);
            } else {
                prestr.append(key).append("=").append(value).append("&");
            }
        }
        return prestr.toString();
    }

    // 以下是加解密相关的函数
    private static byte[] cmbSM2SignWithSM3(byte[] id, byte[] privkey, byte[] msg) throws IOException, CryptoException {
        if (privkey == null || msg == null) {
            throw new CryptoException("CMBSM2SignWithSM3 input error");
        }
        ECPrivateKeyParameters privateKey = encodePrivateKey(privkey);
        SM2Signer signer = new SM2Signer();
        ParametersWithID parameters = new ParametersWithID(privateKey, id);
        signer.init(true, parameters);
        signer.update(msg, 0, msg.length);
        return decodeDERSignature(signer.generateSignature());
    }

    private static ECPrivateKeyParameters encodePrivateKey(byte[] value) {
        BigInteger d = new BigInteger(1, value);
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        ECDomainParameters ecParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
        return new ECPrivateKeyParameters(d, ecParameters);
    }

    private static byte[] decodeDERSignature(byte[] signature) throws IOException {
        ASN1InputStream stream = new ASN1InputStream(new ByteArrayInputStream(signature));
        ASN1Sequence primitive = (ASN1Sequence)stream.readObject();
        Enumeration<ASN1Integer> enumeration = primitive.getObjects();
        BigInteger intR = enumeration.nextElement().getValue();
        BigInteger intS = enumeration.nextElement().getValue();
        byte[] bytes = new byte[LENGTH_32 * 2];
        byte[] r = format(intR.toByteArray());
        byte[] s = format(intS.toByteArray());
        System.arraycopy(r, 0, bytes, 0, LENGTH_32);
        System.arraycopy(s, 0, bytes, LENGTH_32, LENGTH_32);
        return bytes;
    }

    private static byte[] format(byte[] value) {
        if (value.length == LENGTH_32) {
            return value;
        } else {
            byte[] bytes = new byte[LENGTH_32];
            if (value.length > LENGTH_32) {
                System.arraycopy(value, value.length - LENGTH_32, bytes, 0, LENGTH_32);
            } else {
                System.arraycopy(value, 0, bytes, LENGTH_32 - value.length, value.length);
            }
            return bytes;
        }
    }

    private static byte[] cmbSM4Crypt(byte[] key, byte[] iv, byte[] input, int mode) throws GeneralSecurityException {
        SecretKeySpec spec = new SecretKeySpec(key, "SM4");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS7Padding", BouncyCastleProvider.PROVIDER_NAME);
        cipher.init(mode, spec, ivParameterSpec);
        return cipher.doFinal(input);
    }

    private static boolean cmbSM2VerifyWithSM3(byte[] id, byte[] pubkey, byte[] msg, byte[] signature) throws IOException {

        if (pubkey == null || msg == null || signature == null) {
            throw new IllegalArgumentException("CMBSM2VerifyWithSM3 input error");
        }
        ECPublicKeyParameters publicKey = encodePublicKey(pubkey);
        SM2Signer signer = new SM2Signer();
        ParametersWithID parameters = new ParametersWithID(publicKey, id);
        signer.init(false, parameters);
        signer.update(msg, 0, msg.length);
        return signer.verifySignature(encodeDERSignature(signature));
    }

    private static ECPublicKeyParameters encodePublicKey(byte[] value) {
        byte[] x = new byte[LENGTH_32];
        byte[] y = new byte[LENGTH_32];
        System.arraycopy(value, 1, x, 0, LENGTH_32);
        System.arraycopy(value, LENGTH_32 + 1, y, 0, LENGTH_32);
        BigInteger intX = new BigInteger(1, x);
        BigInteger intY = new BigInteger(1, y);
        ECParameterSpec spec = ECNamedCurveTable.getParameterSpec("sm2p256v1");
        ECPoint intQ = spec.getCurve().createPoint(intX, intY);
        ECDomainParameters ecParameters = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH(), spec.getSeed());
        return new ECPublicKeyParameters(intQ, ecParameters);
    }

    private static byte[] encodeDERSignature(byte[] signature) throws IOException {
        byte[] r = new byte[LENGTH_32];
        byte[] s = new byte[LENGTH_32];
        System.arraycopy(signature, 0, r, 0, LENGTH_32);
        System.arraycopy(signature, LENGTH_32, s, 0, LENGTH_32);
        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(new ASN1Integer(new BigInteger(1, r)));
        vector.add(new ASN1Integer(new BigInteger(1, s)));
        return (new DERSequence(vector)).getEncoded();
    }
}
